Skip to content

fix(app-shell): 空态「Create Your First App」CTA 指向已声明的 app 内 create-app 路由 (#3573) - #3588

Merged
yinlianghui merged 2 commits into
mainfrom
claude/issue-3573-create-app-cta
Aug 7, 2026
Merged

fix(app-shell): 空态「Create Your First App」CTA 指向已声明的 app 内 create-app 路由 (#3573)#3588
yinlianghui merged 2 commits into
mainfrom
claude/issue-3573-create-app-cta

Conversation

@yinlianghui

Copy link
Copy Markdown
Collaborator

Fixes #3573

问题

零应用部署的第一屏上,唯一指向「建应用」的 CTA(create-first-app-btn)调用
navigate('/create-app') —— 带前导斜杠的绝对路径,从宿主的路由树解析。
apps/console/src/App.tsx 的根路由树没有声明 /create-app,于是落到末尾的兜底
path="*"replace 回落地页 /。用户看到的是「按钮点了没反应」,没有任何报错。

真正声明 create-app 的是 AppContent 自己,位于 /apps/:appName/* 子树内部
(无 activeApp 分支一处、有 app 的路由表一处)。

实测(先量后改)

① 空态的真实 URL 家族 = /apps/setup 及其子路径。
空态前置条件要求 !activeApp 且不是 create-app / system / metadata 三条伪路由;
要绕过上方的 requestedAppMissing 分支又必须 isSpecialRoute —— 相减后只剩
isSetupRoute。零应用用户从 / 会被 RootLandingRedirect 解析到 /home
(resolveLandingPath([])),AppContent 根本不挂载。空态只在子树内出现,
入口是无 activeApp 时侧边栏的系统导航(AppSidebarsystemFallbackNavigation
/apps/setup),以及空态自己那颗 go-to-settings-btn

② 因此「改成相对跳转」不成立 —— 这是本次唯一的方案偏离,已实测证伪。
派单的首选假设是 navigate('create-app')(相对),理由是相对解析走匹配路由的
pathnameBase(splat 被排除)。装机的 react-router 7.18 不是这个行为:

// react-router/dist/development/chunk-4ZMWKKQ3.mjs:919
function getResolveToMatches(matches) {
  let pathMatches = getPathContributingMatches(matches);
  return pathMatches.map(
    (match, idx) => idx === pathMatches.length - 1 ? match.pathname : match.pathnameBase
  );
}

叶子 match 贡献的是完整 pathname,含 splat(v6 时代这是
v7_relativeSplatPath 未来开关,v7 已固化为默认)。所以相对形式是随深度漂移的:
只在最浅的 /apps/setup 正确,从 /apps/setup/sys_inbox_message 会拼出
/apps/setup/sys_inbox_message/create-app —— 匹配不到无-app 分支里的任何路由,
渲染成白屏,比原来的弹回更难诊断。这一步是实跑出来的,不是推断:相对版本跑出
6 条里挂 1 条,dump 里 pathname 正是 /apps/setup/sys_inbox_message/create-app

改法

构造 app 作用域的 /apps/{appName}/create-app:这是 ADR-0048 的规范应用 URL
(utils/appRoute.ts),也正是 AppSidebar 的 add-app 入口(add-app-btn)已经在用的
同一个目标,以及本文件其余约 8 处导航一致的基址。appName 在该分支上必然存在
(见实测 ①:分支只在 /apps/setup… 下可达)。

测试

新增 packages/app-shell/src/console/__tests__/AppContent.noAppsCta.test.tsx:
MemoryRouter 里复刻宿主根路由树(含那条吃掉 /create-app 的兜底),
在实测出的两种真实 URL 上点 CTA。

反向验证:两条 CTA 断言在未改的源码上先跑红 —— 预测是红,实际也是红,
失败 dump 里 pathname/root-landing 已渲染,正是 issue 描述的弹回:

Tests  2 failed | 4 passed (6)

改后 6/6 全绿。另有两条测试分别钉住「深层 splat URL 解析到同一个
/apps/setup/create-app」(② 的回归闸)和「旁边的 go-to-settings-btn 仍走绝对
/apps/setup」(未触及共享代码的证明),以及一条把 ① 的 URL 家族结论写死的度量测试
(非伪路由的 /apps/:appName 走 App-not-available 分支,永远到不了这个空态)。

  • pnpm exec vitest run packages/app-shell/286 files / 2498 passed, 1 skipped, 0 failed
  • pnpm --filter @object-ui/app-shell type-check → exit 0
  • pnpm exec eslint(改动两文件)→ 0 errors(37 条既有 no-explicit-any warning)
  • node scripts/check-control-bytes.mjs → OK

范围

只动了那一行 navigate 调用(加注释)+ 新测试 + changeset。未触碰
apps/console/src/App.tsx 的根路由(宿主契约变更),也未触碰
content/docs/releases/。全仓搜索确认 create-first-app-btn / /create-app
字面量再无其他消费方(i18n 各 locale 只有文案键)。


🤖 Generated with Claude Code

https://claude.ai/code/session_01GTRjn8xBqp75dk7kFupVRt


Generated by Claude Code

…路由 (#3573)

零应用部署的第一屏上,唯一指向「建应用」的 CTA 调用 navigate('/create-app') ——
带前导斜杠的绝对路径,从宿主的根路由树解析。根路由树没有声明 /create-app,于是落到
末尾的 <Route path="*"> 兜底,被 replace 回落地页 `/`:按钮看起来毫无反应。

真正声明 create-app 的是 AppContent 自己,位于 /apps/:appName/* 子树内(无 activeApp
分支与有 app 的路由表各一处)。改为构造 app 作用域的 /apps/<segment>/create-app ——
ADR-0048 的规范应用 URL,也正是 AppSidebar 的 add-app 入口已经在用的同一目标。

实测记录(新增路由测试逐条钉住):

- 空态的真实 URL 家族是 /apps/setup 及其子路径。空态前置条件要求 !activeApp 且
  非 create-app/system/metadata 三条伪路由;要绕过 requestedAppMissing 又必须
  isSpecialRoute,相减后只剩 isSetupRoute。零应用用户从 `/` 会被
  RootLandingRedirect 送到 /home,AppContent 根本不挂载 —— 空态只在子树内出现。
- 因此「改成相对跳转 navigate('create-app')」不成立:装机的 react-router 7 里
  getResolveToMatches 用 LEAF match 的完整 pathname(含 splat)作为相对解析基
  (v6 的 v7_relativeSplatPath 开关,v7 已固化)。它只在最浅的 /apps/setup 正确,
  从 /apps/setup/<segment> 会拼出 /apps/setup/<segment>/create-app —— 匹配不到
  任何路由,渲染成白屏,比原来的弹回更难诊断。

反向验证:两条 CTA 测试在未改的源码上先跑红(dump 里 pathname 为 `/`、
root-landing 已渲染),改后 6/6 全绿。旁边的 go-to-settings-btn 未触及,
其绝对 /apps/setup 目标另有一条测试守住。

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GTRjn8xBqp75dk7kFupVRt
@vercel

vercel Bot commented Aug 7, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

1 Skipped Deployment
Project Deployment Actions Updated (UTC)
objectui Ignored Ignored Aug 7, 2026 2:54pm

Request Review

该断言原本的注释读起来像在背书 `/apps/setup` 这个目标。实测下来裸
`/apps/setup` 正是空态自己的 URL(isSystemRoute 需要 `/system` 段),
零应用部署下这颗按钮同样是死键 —— 已另立 #3590。此处只用于证明
#3573 的改动没碰它,修 #3590 时应一并更新。

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GTRjn8xBqp75dk7kFupVRt
@github-actions

github-actions Bot commented Aug 7, 2026

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

Metric Value Budget
Main entry (gzip) 28.1 KB 350 KB
Entry file index-CVz6ARkO.js
Status PASS

📦 Bundle Size Report

Package Size Gzipped
app-shell (index.js) 8.66KB 3.13KB
app-shell (runtime-config.js) 7.42KB 2.32KB
app-shell (types.js) 0.01KB 0.04KB
app-shell (urlParams.js) 7.57KB 2.97KB
auth (AuthContext.js) 0.31KB 0.24KB
auth (AuthGuard.js) 1.17KB 0.53KB
auth (AuthProvider.js) 22.10KB 4.37KB
auth (AuthShell.js) 3.49KB 1.40KB
auth (ForgotPasswordForm.js) 12.21KB 3.45KB
auth (LoginForm.js) 18.13KB 5.39KB
auth (PreviewBanner.js) 0.90KB 0.50KB
auth (RegisterForm.js) 6.64KB 2.21KB
auth (SocialSignInButtons.js) 9.60KB 3.89KB
auth (UserMenu.js) 3.40KB 1.22KB
auth (auth-gate-events.js) 1.29KB 0.66KB
auth (authStyles.js) 5.04KB 1.72KB
auth (createAuthClient.js) 35.76KB 9.11KB
auth (createAuthenticatedFetch.js) 4.37KB 1.69KB
auth (index.js) 2.35KB 1.07KB
auth (org-roles.js) 6.66KB 2.78KB
auth (phone-identifier.js) 1.11KB 0.66KB
auth (types.js) 0.59KB 0.35KB
auth (useAuth.js) 4.91KB 0.87KB
auth (useIsWorkspaceAdmin.js) 1.61KB 0.85KB
collaboration (CommentThread.js) 26.07KB 7.56KB
collaboration (LiveCursors.js) 3.17KB 1.27KB
collaboration (PresenceAvatars.js) 6.49KB 2.64KB
collaboration (PresenceProvider.js) 2.79KB 1.13KB
collaboration (index.js) 1.65KB 0.73KB
collaboration (useCollaborationTranslation.js) 6.05KB 2.52KB
collaboration (useCommentSearch.js) 1.98KB 0.88KB
collaboration (useConflictResolution.js) 7.75KB 1.86KB
collaboration (useMentionNotifications.js) 1.81KB 0.68KB
collaboration (usePresence.js) 6.33KB 1.84KB
collaboration (useRealtimeSubscription.js) 7.91KB 2.01KB
components (index.js) 480.72KB 105.64KB
core (index.js) 2.96KB 1.13KB
create-plugin (index.js) 9.28KB 2.98KB
data-objectstack (index.js) 137.51KB 35.11KB
fields (index.js) 230.87KB 56.83KB
i18n (LocalizationContext.js) 1.76KB 0.96KB
i18n (currency.js) 1.22KB 0.64KB
i18n (i18n.js) 4.32KB 1.77KB
i18n (index.js) 2.65KB 1.06KB
i18n (pickLocalized.js) 1.70KB 0.83KB
i18n (provider.js) 9.48KB 3.27KB
i18n (useObjectLabel.js) 26.14KB 6.07KB
i18n (useSafeTranslation.js) 4.52KB 1.96KB
layout (index.js) 38.53KB 10.71KB
mobile (MobileProvider.js) 0.92KB 0.49KB
mobile (ResponsiveContainer.js) 0.94KB 0.38KB
mobile (breakpoints.js) 1.51KB 0.70KB
mobile (createOfflineDataSource.js) 5.61KB 1.74KB
mobile (index.js) 1.50KB 0.62KB
mobile (offlineQueue.js) 3.91KB 1.35KB
mobile (pwa.js) 0.97KB 0.49KB
mobile (serviceWorker.js) 1.48KB 0.62KB
mobile (serviceWorkerSource.js) 3.41KB 1.48KB
mobile (useBreakpoint.js) 1.54KB 0.65KB
mobile (useGesture.js) 6.96KB 1.98KB
mobile (useOfflineSync.js) 1.99KB 0.72KB
mobile (usePullToRefresh.js) 2.53KB 0.85KB
mobile (useResponsive.js) 0.71KB 0.42KB
mobile (useResponsiveConfig.js) 1.36KB 0.63KB
mobile (useSpecGesture.js) 4.05KB 1.53KB
mobile (useTouchTarget.js) 1.01KB 0.54KB
permissions (MePermissionsProvider.js) 8.75KB 3.06KB
permissions (PermissionContext.js) 0.31KB 0.25KB
permissions (PermissionGuard.js) 0.89KB 0.45KB
permissions (PermissionProvider.js) 3.67KB 1.12KB
permissions (evaluator.js) 4.41KB 1.44KB
permissions (index.js) 0.91KB 0.41KB
permissions (store.js) 0.91KB 0.42KB
permissions (useFieldPermissions.js) 1.28KB 0.52KB
permissions (usePermissions.js) 1.55KB 0.71KB
plugin-ai (index.js) 15.71KB 3.79KB
plugin-calendar (index.js) 44.98KB 12.37KB
plugin-charts (index.js) 61.04KB 17.31KB
plugin-chatbot (index.js) 180.09KB 42.72KB
plugin-dashboard (index.js) 112.03KB 28.88KB
plugin-designer (index.js) 210.51KB 42.51KB
plugin-detail (index.js) 232.79KB 57.42KB
plugin-editor (index.js) 2.46KB 1.10KB
plugin-form (index.js) 112.10KB 27.10KB
plugin-gantt (index.js) 162.55KB 39.57KB
plugin-grid (index.js) 186.61KB 49.34KB
plugin-kanban (index.js) 48.30KB 13.28KB
plugin-list (index.js) 105.12KB 25.48KB
plugin-map (index.js) 16.81KB 5.24KB
plugin-markdown (index.js) 13.72KB 4.69KB
plugin-report (index.js) 40.58KB 10.58KB
plugin-timeline (index.js) 25.76KB 7.33KB
plugin-tree (index.js) 8.50KB 2.88KB
plugin-view (index.js) 84.03KB 20.55KB
providers (DataSourceProvider.js) 0.75KB 0.39KB
providers (MetadataProvider.js) 1.37KB 0.59KB
providers (ThemeProvider.js) 1.90KB 0.85KB
providers (UploadProvider.js) 11.71KB 3.53KB
providers (index.js) 0.44KB 0.22KB
providers (types.js) 0.01KB 0.04KB
react-runtime (index.js) 5.67KB 2.37KB
react (LazyPluginLoader.js) 3.77KB 1.33KB
react (SchemaRenderer.js) 19.28KB 6.38KB
react (data-invalidation.js) 5.05KB 2.08KB
react (index.js) 1.02KB 0.55KB
react (spec-input.js) 0.20KB 0.18KB
sdui-parser (codegen.js) 4.09KB 1.74KB
sdui-parser (index.js) 4.47KB 2.03KB
sdui-parser (parse.js) 10.04KB 2.82KB
sdui-parser (types.js) 0.29KB 0.24KB
sdui-parser (validate.js) 4.69KB 1.48KB
types (ai.js) 0.20KB 0.17KB
types (api-types.js) 0.20KB 0.18KB
types (app.js) 2.87KB 0.99KB
types (base.js) 0.20KB 0.18KB
types (blocks.js) 0.20KB 0.18KB
types (complex.js) 0.20KB 0.18KB
types (crud.js) 0.20KB 0.18KB
types (data-display.js) 0.20KB 0.18KB
types (data-protocol.js) 0.20KB 0.19KB
types (data.js) 0.20KB 0.18KB
types (designer.js) 1.87KB 0.85KB
types (disclosure.js) 0.20KB 0.18KB
types (error-code.js) 1.54KB 0.88KB
types (feedback.js) 0.20KB 0.18KB
types (field-types.js) 0.20KB 0.18KB
types (form.js) 0.20KB 0.18KB
types (http-retry.js) 4.32KB 2.02KB
types (index.js) 2.71KB 1.34KB
types (layout.js) 0.20KB 0.18KB
types (managed-by.js) 0.19KB 0.18KB
types (mobile.js) 2.54KB 1.30KB
types (navigation.js) 0.20KB 0.18KB
types (objectql.js) 0.20KB 0.18KB
types (overlay.js) 0.20KB 0.18KB
types (permissions.js) 0.20KB 0.18KB
types (plugin-scope.js) 0.20KB 0.18KB
types (record-components.js) 0.20KB 0.19KB
types (record-semantics.js) 1.28KB 0.67KB
types (registry.js) 0.20KB 0.18KB
types (reports.js) 0.20KB 0.18KB
types (spec-report.js) 5.05KB 1.93KB
types (system-fields.js) 3.33KB 1.54KB
types (theme.js) 0.20KB 0.18KB
types (ui-action.js) 3.40KB 1.71KB
types (views.js) 0.20KB 0.18KB
types (widget.js) 0.20KB 0.18KB

Size Limits

  • ✅ Core packages should be < 50KB gzipped
  • ✅ Component packages should be < 100KB gzipped
  • ⚠️ Plugin packages should be < 150KB gzipped

@yinlianghui
yinlianghui marked this pull request as ready for review August 7, 2026 14:58
@yinlianghui
yinlianghui added this pull request to the merge queue Aug 7, 2026
Merged via the queue into main with commit 9089d85 Aug 7, 2026
19 checks passed
@yinlianghui
yinlianghui deleted the claude/issue-3573-create-app-cta branch August 7, 2026 14:59
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

console: 空态「Create Your First App」CTA 跳向未声明的 /create-app,被 catch-all 弹回落地页

2 participants